﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SpeakupBackend.Themes;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace SpeakupTests.SpeakupBackend.Themes
{
    [TestClass]
    public class WeekTests
    {
        [TestMethod]
        public void IsWeekNumberValid()
        {
            //confirm all valid values are recognized as valid
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                Assert.IsTrue(Week.IsValidWeekNumber(i));
            }

            //confirm all invalid values are recognized as invalid
            for (int i = Week.MinValidWeekNumber - 10; i < Week.MinValidWeekNumber; i++)
            {
                Assert.IsFalse(Week.IsValidWeekNumber(i));
            }

            for (int i = Week.MaxValidWeekNumber + 10; i > Week.MaxValidWeekNumber; i--)
            {
                Assert.IsFalse(Week.IsValidWeekNumber(i));
            }
        }

        [TestMethod]
        public void GetWeeks()
        {
            IList<Week> list = Week.GetWeeks();

            //verify that number of elements is correct
            Assert.AreEqual(list.Count, Week.MaxValidWeekNumber - Week.MinValidWeekNumber + 1);

            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                int count = (from n in list
                             where n.WeekNumber == i
                             select n).Count();

                //expect exactly 1 found item
                Assert.AreEqual(count, 1);
            }
        }

        [TestMethod]
        public void GetWeekByNumber()
        {
            //verify that valid weeks can be obtained
            for(int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                Week w = Week.GetWeek(i);
                Assert.AreEqual(w.WeekNumber, i);
            }

            //verify that invalid weeks can not be obtained
            for (int i = Week.MinValidWeekNumber - 10; i < Week.MinValidWeekNumber; i++)
            {
                bool thrown = false;
                try
                {
                    Week.GetWeek(i);
                }
                catch(ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Week.MaxValidWeekNumber + 10; i > Week.MaxValidWeekNumber; i--)
            {
                bool thrown = false;
                try
                {
                    Week.GetWeek(i);
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

        }

        [TestMethod]
        public void GetWeekByString()
        {
            //verify that valid weeks can be obtained
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                Week w = Week.GetWeek(i.ToString());
                Assert.AreEqual(w.WeekNumber, i);
            }

            //verify that invalid weeks can not be obtained
            for (int i = Week.MinValidWeekNumber - 10; i < Week.MinValidWeekNumber; i++)
            {
                bool thrown = false;
                try
                {
                    Week.GetWeek(i.ToString());
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Week.MaxValidWeekNumber + 10; i > Week.MaxValidWeekNumber; i--)
            {
                bool thrown = false;
                try
                {
                    Week.GetWeek(i.ToString());
                }
                catch (ArgumentException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void GetWeekByInvalidString()
        {
            Random rnd = new Random();
            
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                bool thrown = false;
                try
                {
                    String strWeek = String.Format("{1}{0}", (char)rnd.Next('a', 'z'), i);
                    Week.GetWeek( strWeek );
                }
                catch (FormatException)
                {
                    thrown = true;
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void CreateWeek()
        {
            //create week with valid week number
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                PrivateObject privateObject = new PrivateObject(typeof(Week), i);
                Week week = (Week)privateObject.Target;
                Assert.AreEqual(i, week.WeekNumber);
            }

            //verify that invalid weeks can not be created
            for (int i = Week.MinValidWeekNumber - 10; i < Week.MinValidWeekNumber; i++)
            {
                bool thrown = false;
                try
                {
                    PrivateObject privateObject = new PrivateObject(typeof(Week), i);
                }
                catch (TargetInvocationException exception)
                {
                    if (exception.InnerException.GetType() == typeof(ArgumentException))
                    {
                        thrown = true;
                    }                    
                }

                Assert.AreEqual(true, thrown);
            }

            for (int i = Week.MaxValidWeekNumber + 10; i > Week.MaxValidWeekNumber; i--)
            {
                bool thrown = false;
                try
                {
                    PrivateObject privateObject = new PrivateObject(typeof(Week), i);
                }
                catch (TargetInvocationException exception)
                {
                    if (exception.InnerException.GetType() == typeof(ArgumentException))
                    {
                        thrown = true;
                    }
                }

                Assert.AreEqual(true, thrown);
            }
        }

        [TestMethod]
        public void WeekToString()
        {
            //verify that each week can be deserializedWeek from string.
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                Week original = Week.GetWeek(i);
                Week fromString = Week.GetWeek(original.ToString());

                Assert.AreEqual(original.WeekNumber, fromString.WeekNumber);
            }
        }

        [TestMethod]
        public void WeekObjectEquals()
        {
            //verify that all equal object return true for Equals (non-generic)
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                PrivateObject privateWeekA = new PrivateObject(typeof(Week), i);
                PrivateObject privateWeekB = new PrivateObject(typeof(Week), i);

                Assert.IsTrue(privateWeekA.Target.Equals(privateWeekB.Target));
            }

            //verify that all non-equal return false for Equals (non-generic)
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                for (int j = Week.MinValidWeekNumber; j <= Week.MaxValidWeekNumber; j++)
                {
                    if (i == j) continue;

                    PrivateObject privateWeekA = new PrivateObject(typeof(Week), i);
                    PrivateObject privateWeekB = new PrivateObject(typeof(Week), j);

                    Assert.IsFalse(privateWeekA.Target.Equals(privateWeekB.Target));
                }
            }

            //verify true for same object
            Week sameRefVerifier = Week.GetWeek(Week.MinValidWeekNumber);
            Object sameWeek = sameRefVerifier;
            Assert.IsTrue( sameRefVerifier.Equals(sameWeek) );

            //verify that fasle returned for null
            Week nullVerifier = Week.GetWeek(Week.MinValidWeekNumber);
            Object nullObject = null;
            Assert.IsFalse(nullVerifier.Equals(nullObject));

            //verify false is returned for non-week objects
            Week otherObjectVerifier = Week.GetWeek(Week.MinValidWeekNumber);
            Object otherObject = new Object();

            Assert.IsFalse( otherObjectVerifier.Equals( otherObject ) );
        }

        [TestMethod]
        public void WeekIEquatableEquals()
        {
            //verify that all equal object return true for Equals (non-generic)
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                PrivateObject privateWeekA = new PrivateObject(typeof(Week), i);
                PrivateObject privateWeekB = new PrivateObject(typeof(Week), i);

                Week weekA = (Week)privateWeekA.Target;
                Week weekB = (Week)privateWeekB.Target;

                Assert.IsTrue(weekA.Equals(weekB));
            }

            //verify that all non-equal return false for Equals (non-generic)
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                for (int j = Week.MinValidWeekNumber; j <= Week.MaxValidWeekNumber; j++)
                {
                    if (i == j) continue;

                    PrivateObject privateWeekA = new PrivateObject(typeof(Week), i);
                    PrivateObject privateWeekB = new PrivateObject(typeof(Week), j);

                    Week weekA = (Week)privateWeekA.Target;
                    Week weekB = (Week)privateWeekB.Target;

                    Assert.IsFalse(weekA.Equals(weekB));
                }
            }

            //verify true for same object
            Week sameRefVerifier = Week.GetWeek(Week.MinValidWeekNumber);
            Assert.IsTrue(sameRefVerifier.Equals(sameRefVerifier));

            //verify that fasle returned for null
            Week nullVerifier = Week.GetWeek(Week.MinValidWeekNumber);
            Week nullWeek = null;
            Assert.IsFalse(nullVerifier.Equals(nullWeek));
        }

        [TestMethod]
        public void WeekGetHashCode()
        {
            Dictionary<int, Week> weekDict = new Dictionary<int, Week>();

            //verify that no two weeks have same hash code
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                Week week = Week.GetWeek(i);
                Assert.IsFalse( weekDict.Keys.Contains(week.GetHashCode()) );

                weekDict[week.GetHashCode()] = week;
            }
        }

        [TestMethod]
        public void WeekObjectCompareTo()
        {
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                for (int j = Week.MinValidWeekNumber; j <= Week.MaxValidWeekNumber; j++)
                {
                    Week iWeek = Week.GetWeek(i);
                    Week jWeek = Week.GetWeek(j);

                    int i2j = iWeek.CompareTo((Object)jWeek);
                    int j2i = jWeek.CompareTo((Object)iWeek);

                    if (i < j)
                    {
                        Assert.IsTrue(i2j < 0);
                        Assert.IsTrue(j2i > 0);
                    }
                    else if (i > j)
                    {
                        Assert.IsTrue(i2j > 0);
                        Assert.IsTrue(j2i < 0);
                    }
                    else
                    {
                        Assert.AreEqual(0, i2j);
                        Assert.AreEqual(0, j2i);
                    }

                }
            }

        }

        [TestMethod]
        public void WeekGenericCompareTo()
        {
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                for (int j = Week.MinValidWeekNumber; j <= Week.MaxValidWeekNumber; j++)
                {
                    Week iWeek = Week.GetWeek(i);
                    Week jWeek = Week.GetWeek(j);

                    int i2j = iWeek.CompareTo(jWeek);
                    int j2i = jWeek.CompareTo(iWeek);

                    if (i < j)
                    {
                        Assert.IsTrue(i2j < 0);
                        Assert.IsTrue(j2i > 0);
                    }
                    else if (i > j)
                    {
                        Assert.IsTrue(i2j > 0);
                        Assert.IsTrue(j2i < 0);
                    }
                    else
                    {
                        Assert.AreEqual(0, i2j);
                        Assert.AreEqual(0, j2i);
                    }

                }
            }
        }

        [TestMethod]
        public void WeekComaprisonOperators()
        {
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                for (int j = Week.MinValidWeekNumber; j <= Week.MaxValidWeekNumber; j++)
                {
                    Week iWeek = Week.GetWeek(i);
                    Week jWeek = Week.GetWeek(j);

                    Assert.AreEqual(i != j, iWeek != jWeek);
                    Assert.AreEqual(i == j, iWeek == jWeek);
                    Assert.AreEqual(i <  j, iWeek <  jWeek);
                    Assert.AreEqual(i <= j, iWeek <= jWeek);
                    Assert.AreEqual(i >  j, iWeek >  jWeek);
                    Assert.AreEqual(i >= j, iWeek >= jWeek);

                    Assert.AreEqual(j != i, jWeek != iWeek);
                    Assert.AreEqual(j == i, jWeek == iWeek);
                    Assert.AreEqual(j <  i, jWeek <  iWeek);
                    Assert.AreEqual(j <= i, jWeek <= iWeek);
                    Assert.AreEqual(j >  i, jWeek >  iWeek);
                    Assert.AreEqual(j >= i, jWeek >= iWeek);
                }
            }

            Week iNullWeek = null;
            Week jNullWeek = null;
            Week nonNullWeek = Week.GetWeek(Week.MinValidWeekNumber);
            
            Assert.IsTrue(iNullWeek == jNullWeek);
            Assert.IsTrue(jNullWeek == iNullWeek);
            
            Assert.IsFalse(iNullWeek != jNullWeek);
            Assert.IsFalse(jNullWeek != iNullWeek);

            Assert.IsFalse(nonNullWeek == iNullWeek);
            Assert.IsFalse(iNullWeek == nonNullWeek);

            Assert.IsTrue(nonNullWeek != iNullWeek);
            Assert.IsTrue(iNullWeek != nonNullWeek);
        }

        [TestMethod]
        public void WeekSerialization()
        {
            BinaryFormatter formatter = new BinaryFormatter();
            
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++)
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                    Week week = Week.GetWeek(i);

                    formatter.Serialize(memStream, week);
                    memStream.Position = 0;
                    Week deserializedWeek = (Week)formatter.Deserialize(memStream);

                    Assert.AreEqual(week.WeekNumber, deserializedWeek.WeekNumber);
                    Assert.IsTrue( Object.ReferenceEquals( week, deserializedWeek) );
                }
            }
        }

        [TestMethod]
        public void WeekMoveToMonday()
        {
            DateTime now = DateTime.Now;

            DateTime begin = new DateTime(now.Year    , 1, 1, 0, 0, 0);
            DateTime end   = new DateTime(now.Year + 3, 1, 1, 0, 0, 0);
            
            PrivateType weekType = new PrivateType(typeof(Week));

            for (DateTime cur = begin; cur < end; cur = cur + TimeSpan.FromDays(1))
            {
                DateTime expectedResult = cur;
                while (expectedResult.DayOfWeek != DayOfWeek.Monday)
                {
                    expectedResult = expectedResult - TimeSpan.FromDays(1);
                }

                DateTime actualResult = (DateTime)weekType.InvokeStatic("MoveToMonday", cur);

                Assert.AreEqual(expectedResult, actualResult);
            }
        }

        [TestMethod]
        public void WeekMoveToFriday()
        {
            DateTime now = DateTime.Now;

            DateTime begin = new DateTime(now.Year, 1, 1, 0, 0, 0);
            DateTime end = new DateTime(now.Year + 3, 1, 1, 0, 0, 0);

            PrivateType weekType = new PrivateType(typeof(Week));

            for (DateTime cur = begin; cur < end; cur = cur + TimeSpan.FromDays(1))
            {
                DateTime expectedResult = cur;
                while (expectedResult.DayOfWeek != DayOfWeek.Sunday)
                {
                    expectedResult = expectedResult + TimeSpan.FromDays(1);
                }

                DateTime actualResult = (DateTime)weekType.InvokeStatic("MoveToSunday", cur);

                Assert.AreEqual(expectedResult, actualResult);
            }
        }

        [TestMethod]
        public void GetWeekByDate()
        {
            DateTime now = DateTime.Now;

            DateTime begin = new DateTime(now.Year, 1, 1, 0, 0, 0);
            DateTime end = new DateTime(now.Year + 3, 1, 1, 0, 0, 0);

            int expectedWeek = 1;

            for (DateTime cur = begin; cur < end; cur = cur + TimeSpan.FromDays(1))
            {
                Assert.AreEqual(Week.GetWeek(expectedWeek), Week.GetWeek(begin, 1, cur));

                if (cur.DayOfWeek == DayOfWeek.Sunday)
                {
                    expectedWeek++;
                    if (expectedWeek > 12)
                    {
                        expectedWeek = 1;
                    }
                }
            }            
        }

    }
}
